#include "CSTBoy.h"
#include "commons.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

////////////////////////////////////////////////////////////////////////////////////////////////////

uint8_t SingletonReadMemory( uint16_t address ) {
    return CSTBoy::GetSingleton()->ReadMemory( address );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void SingletonWriteMemory( uint16_t address, uint8_t value ) {
    CSTBoy::GetSingleton()->WriteMemory( address, value );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

CSTBoy* CSTBoy::mInstance = NULL;

////////////////////////////////////////////////////////////////////////////////////////////////////

CSTBoy::CSTBoy() {
    mZ80 = new CZ80();
    mZ80->SetReadPtr( &SingletonReadMemory );
    mZ80->SetWritePtr( &SingletonWriteMemory );

    mPPU = new CPPU();
    mPPU->SetReadPtr( &SingletonReadMemory );
    mPPU->SetWritePtr( &SingletonWriteMemory );

    mROM = ( uint8_t* )malloc( ROM_SIZE );
    if( !mROM ) {
        printf( "\nError: no se pudo alojar la memoria necesaria" );
        exit( 0 );
    }
    memset( mROM, 0, ROM_SIZE );
    mROM[ 0xff05 ] = 0x00;
    mROM[ 0xff06 ] = 0x00;
    mROM[ 0xff07 ] = 0x00;
    mROM[ 0xff10 ] = 0x80;
    mROM[ 0xff11 ] = 0xbf;
    mROM[ 0xff12 ] = 0xf3;
    mROM[ 0xff14 ] = 0xbf;
    mROM[ 0xff16 ] = 0x3f;
    mROM[ 0xff17 ] = 0x00;
    mROM[ 0xff19 ] = 0xbf;
    mROM[ 0xff1a ] = 0x7f;
    mROM[ 0xff1b ] = 0xff;
    mROM[ 0xff1c ] = 0x9f;
    mROM[ 0xff1e ] = 0xbf;
    mROM[ 0xff20 ] = 0xff;
    mROM[ 0xff21 ] = 0x00;
    mROM[ 0xff22 ] = 0x00;
    mROM[ 0xff23 ] = 0xbf;
    mROM[ 0xff24 ] = 0x77;
    mROM[ 0xff25 ] = 0xf3;
    mROM[ 0xff26 ] = 0xf1;
    mROM[ 0xff40 ] = 0x91;
    mROM[ 0xff42 ] = 0x00;
    mROM[ 0xff43 ] = 0x00;
    mROM[ 0xff45 ] = 0x00;
    mROM[ 0xff47 ] = 0xfc;
    mROM[ 0xff48 ] = 0xff;
    mROM[ 0xff49 ] = 0xff;
    mROM[ 0xff4a ] = 0x00;
    mROM[ 0xff4b ] = 0x00;
    mROM[ 0xffff ] = 0x00;

    mIME       = false;
    mPendingEI = 0;
    mPendingDI = 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

CSTBoy::~CSTBoy() {
    delete mZ80;
    delete[] mROM;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

CSTBoy* CSTBoy::GetSingleton() {
    if( !mInstance ) mInstance = new CSTBoy();

    return mInstance;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CSTBoy::LoadGame( const char path[] ) {
    FILE* fp = fopen( path, "rb" );
    printf( "\nAbriendo archivo <%s>", path );
    if( !fp ) {
        printf( "\nError: no se encontro el archivo <%s>", path );
        return false;
    }
    fseek( fp, 0x147, 0 );
    int rom_banks = fgetc( fp );
    int ram_banks = fgetc( fp );
    printf( "\n<%i> bancos de memoria ROM\n<%i> bancos de memoria RAM", rom_banks, ram_banks );
    if( rom_banks != 0 ) {
        fclose( fp );
        printf( "\nError: demasiados bancos de memoria ROM" );
        return false;
    }
    rewind( fp );
    fread( mROM, 1, ROM_SIZE, fp );
    fclose( fp );

    return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

uint8_t CSTBoy::ReadMemory( uint16_t address ) {
    switch( address ) {
        default: return mROM[ address ];
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CSTBoy::WriteMemory( uint16_t address, uint8_t value ) {
    switch( address ) {
        case 0xff44: // LY
            // cualquier escritura a este registro lo resetea
            mROM[ address  ] = 0; 
            break;

        default: mROM[ address ] = value; break;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CSTBoy::Run() {
    int total_cycles = 0, scanline_cycles = 0;

    for( int i = 0; i < 100000; i++ ) {
        uint8_t opcode    = mZ80->Fetch();
        int opcode_cycles = mZ80->ExecuteOpcode( opcode );

        total_cycles    += opcode_cycles;
        scanline_cycles += opcode_cycles;

        // actualizo los graficos
        if( scanline_cycles >= PPU_SCANLINE_CYCLES ) {
            // $FF44 se escribe directamente, pues una escritura a traves del memory handler lo pondria a 0
            mROM[ 0xff44 ]++;
            scanline_cycles -= PPU_SCANLINE_CYCLES;
        }
        mPPU->Update( opcode_cycles );

        // actualizo las interrupciones
        if     ( opcode == 0xfb ) mPendingEI = 1; // EI
        else if( opcode == 0xfe ) mPendingDI = 1; // DI

        if     ( mPendingEI == 1 ) mPendingEI++;
        else if( mPendingEI == 2 ) {
            mPendingEI = 0;
            mIME       = true;
        }

        if     ( mPendingDI == 1 ) mPendingDI++;
        else if( mPendingDI == 2 ) {
            mPendingDI = 0;
            mIME       = false;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////